<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>


  <script>
    const TriggerType = Object.freeze({
      SET: 'SET',
      ADD: 'ADD',
      DELETE: 'DELETE'
    })

    const ITERATE_KEY = Symbol()
    const MAP_KEY_ITERATE_KEY = Symbol()

    let activeEffect

    const effectStack = []

    function effect (fn, options = {}) {

      const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        effectStack.push(effectFn)
        const res = fn()
        effectStack.pop()

        activeEffect = effectStack[effectStack.length - 1]

        return res
      }


      effectFn.options = options
      effectFn.deps = []

      if (!effectFn.options.lazy) {
        effectFn()
      }

      return effectFn
    }

    function cleanup (effectFn) {
      for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
      }

      effectFn.deps.length = 0
    }

    const bucket = new WeakMap()

    const track = (target, key) => {

      // 禁止追踪则直接返回
      if (!activeEffect || !shouldTrack) return;

      if (!activeEffect) {
        return target[key]
      }

      // 1. 获取当前对象对应的依赖集合
      let depsMap = bucket.get(target)

      if (!depsMap) {
        depsMap = new Map()
        bucket.set(target, depsMap)
      }

      // 2. 获取当前属性对应的依赖集合
      let deps = depsMap.get(key)

      if (!deps) {
        deps = new Set()
        depsMap.set(key, deps)
      }

      deps.add(activeEffect)

      activeEffect.deps.push(deps)

    }

    const trigger = (target, key, type, newValue) => {
      // 1. 获取当前对象对应的依赖集合
      const depsMap = bucket.get(target)
      if (!depsMap) return

      // 2. 获取当前属性对应的依赖集合
      const effects = depsMap.get(key)

      // 3. 执行依赖函数
      const effectsToRun = new Set()
      effects && effects.forEach((fn) => {
        if (activeEffect !== fn) { // 避免自己执行自己 无限递归爆栈
          effectsToRun.add(fn)
        }
      })

      // 类型是 添加或删除 且target 是map 的时候 
      if(
        (
          type === TriggerType.ADD ||
          type === TriggerType.DELETE 
        ) && (Object.prototype.toString.call(target) === '[object Map]') 
      ){
        const iterateEffects = depsMap.get(MAP_KEY_ITERATE_KEY)
        iterateEffects && iterateEffects.forEach((effectFn) => {
          if (activeEffect!== effectFn) { // 避免自己执行自己 无限递归爆栈
            effectsToRun.add(effectFn)
          }
        })

      }


      if (
        type === TriggerType.ADD || 
        type === TriggerType.DELETE ||
        (type ===TriggerType.SET && Object.prototype.toString.call(target) === '[object Map]')
      ) { // 如果是添加 或 删除
        const iterateEffects = depsMap.get(ITERATE_KEY)
        iterateEffects && iterateEffects.forEach((effectFn) => {
          if (activeEffect !== effectFn) { // 避免自己执行自己 无限递归爆栈
            effectsToRun.add(effectFn)
          }
        })
      }

      // 当操作类型是 添加  且代理对象是数组的时候 取出并执行和length属性相关联的副作用函数
      if (type === TriggerType.ADD && Array.isArray(target)) {
        const lengthEffects = depsMap.get('length')
        lengthEffects && lengthEffects.forEach((effectFn) => {
          if (activeEffect !== effectFn) { // 避免自己执行自己 无限递归爆栈
            effectsToRun.add(effectFn)
          }
        })
      }


      // 数组 且 操作length
      if (Array.isArray(target) && key === 'length') {

        depsMap.forEach((effects, key) => {
          // 当索引大于或等于 新的length值 则取出并执行和索引相关的副作用函数
          // 疑惑点key: 当访问length时就是length 当通过索引访问元素的时候就是索引值
          if (key >= newValue) {

            effects.forEach((effectFn) => {
              if (activeEffect !== effectFn) { // 避免自己执行自己 无限递归爆栈
                effectsToRun.add(effectFn)
              }
            })
          }
        })
      }


      effectsToRun && effectsToRun.forEach((effectFn) => {
        // 如果存在调度器 则调用调度器 把函数作为参数传入
        if (effectFn.options.scheduler) {
          effectFn.options.scheduler(effectFn)
        } else {
          effectFn()
        }
      })
    }


    // 重寫 數組方法
    const arrayInstrumentations = {};

    ['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
      const originMethod = Array.prototype[method]
      arrayInstrumentations[method] = function (...args) {
        // this是代理对象，先在代理对象中查找
        let res = originMethod.apply(this, args)

        //如果返回false 则表示 找不到， 通过this.raw 拿到原数组  再去其中查找并更新res值
        if (res === false) {
          res = originMethod.apply(this.raw, args)
        }

        return res
      }
    })

    // 标记变量 代表是否进行追踪 默认值为true 允许追踪
    let shouldTrack = true;

    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
      // 获取原始的数组方法
      const originMethod = Array.prototype[method]
      arrayInstrumentations[method] = function (...args) {
        // 调用原始方法的时候禁止追踪
        shouldTrack = false
        let res = originMethod.apply(this, args)

        // 调用原始方法后 就恢复原来行为 允许追踪
        shouldTrack = true

        return res
      }

    })

    const mutableInstrumentations = {
      add (key) {
        // this 只向 的是代理对象
        const target = this.raw
        const hasKey = target.has(key)
        // 通过原始数据对象执行add方法 
        const res = target.add(key)

        // 如果没有key 则触发响应式
        if (!hasKey) {
          // 调用trigger 函数触发响应  并指定操作类型为ADD
          trigger(target, key, TriggerType.ADD)
        }

        return res
      },

      delete (key) {
        const target = this.raw
        const hasKey = target.has(key)
        const res = target.delete(key)

        if (hasKey) {
          trigger(target, key, TriggerType.DELETE)
        }

        return res
      },

      get (key) {
        const target = this.raw
        const hasKey = target.has(key)
        // 追踪依赖,触发响应式联系
        track(target, key)

        // 返回响应式数据
        if (hasKey) {
          const res = target.get(key)
          return typeof res === "object" ? reactive(res) : res
        }
      },

      set (key, value) {
        const target = this.raw
        const had = target.has(key)
        // 获取旧值 
        const oldValue = target.get(key)

        // target.set(key,value)
        // 获取原始数据 防止数据污染
        const rawValue = value.raw || value
        target.set(key, rawValue)

        if (!had) {
          trigger(target, key, TriggerType.ADD)
        } else if (oldValue !== value || (oldValue === oldValue && value === value)) {
          // 如果不存在 且 值变了就是set 意味着修改
          trigger(target, key, TriggerType.SET)
        }
      },

      // 允许接受第二个参数
      forEach (callback, thisArg) {

        //当参数为非原始数据时,无响应式
        // const target = this.raw
        // track(target, ITERATE_KEY)
        // target.forEach(callback)

        // 修正
        const wrap = (val) => typeof val === 'object' ? reactive(val) : val
        const target = this.raw
        track(target, ITERATE_KEY)

        target.forEach((value, key) => {
          // 手动调用callback 将参数进行响应式处理 并将结果返回
          callback.call(thisArg, wrap(value), wrap(key), this)
        })
      }
    }


    // 在 createReactive 里封装用于代理 Set/Map 类型数据的逻辑 
    function createReactive (obj, isShallow = false, isReadonly = false) {
      return new Proxy(obj, {
        // target: 当前代理的目标对象
        // key: 当前要代理的属性
        // receiver: 当前代理的对象
        get (target, key, receiver) {

          // 访问raw属性 则返回原始对象
          if (key === 'raw') {
            return target
          }

          // 如果是访问size 属性， 
          // 则指定第三个参数receiver，为原始对象target
          if (key === 'size') {
            // 建立 响应式联系
            track(target, ITERATE_KEY)
            return Reflect.get(target, key, target)
          }

          // return target[key].bind(target)
          // 返回定义在 mutableInstrumentations 对象下的自定义方法
          return mutableInstrumentations[key]
        },
      })
    }

    // 定义 一个map 实例， 存储原始对象到代理对象的映射
    const reactiveMap = new Map()

    function reactive (obj) {

      // 如果这个对象已经被代理过了，就直接返回代理对象
      const existProxy = reactiveMap.get(obj)
      if (existProxy) return existProxy

      // 否则，创建新的代理对象
      const proxy = createReactive(obj)
      // 存储到map中， 避免重复创建
      reactiveMap.set(obj, proxy)

      return proxy
    }

    function shallowReactive (obj) {
      return createReactive(obj, true)
    }

    function readonly (obj) {
      return createReactive(obj, false, true)
    }

    function shallowReadonly (obj) {
      return createReactive(obj, true, true)
    }


    // const s = new Set([1, 2, 3])
    // const p = createReactive(s)
    // effect(() => {
    //   console.log('%c [  ]-250', 'font-size:13px; background:pink; color:#bf2c9f;', p.size);
    // })

    // p.add(1) // 不输出
    // p.delete(1) // 输出
    // p.delete(9) // 不输出

    // const m = new Map()
    // const p1 = reactive(m)

    // const p2 = reactive(new Map())

    // p1.set('p2',p2)

    // effect(()=>{

    //   console.log('%c [ p2.size ]-334', 'font-size:13px; background:pink; color:#bf2c9f;', m.get('p2').size);
    // })

    // m.get('p2').set('foo',1)

    // const p = reactive(new Map([
    //   [{ key: 1 }, { value: 1 }]
    // ]))

    // effect(() => {
    //   p.forEach(function (value, key, map) {
    //     console.log('%c [ value ]-350', 'font-size:13px; background:pink; color:#bf2c9f;', value);
    //     console.log('%c [ key ]-350', 'font-size:13px; background:pink; color:#bf2c9f;', key);
    //   })
    // })
    // // 有响应式
    // p.set({ key: 2 }, { value: 2 })

    // const key = { key: 1 }
    // const value = new Set([1, 2, 3])
    // const p = reactive(new Map([
    //   [key, value]
    // ]))

    // effect(() => {
    //   p.forEach((value, key, map) => {

    //     console.log('%c [  ]-366', 'font-size:13px; background:pink; color:#bf2c9f;', value.size);
    //   })
    // })

    // p.get(key).delete(1)
    // p.get(key).add(4)


    const p = reactive(new Map([
      ['key',1]

    ]))

    effect(()=>{
      p.forEach(function(v,k){

        console.log('%c [  ]-397', 'font-size:13px; background:pink; color:#bf2c9f;', v);
      })
    })

    p.set('key',2)






  </script>
</body>.

</html>